/*
 * File     : Utf8Encoder.java
 * Created  : 4 May 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.string;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;

import com.googlecode.dni.internal.DniInternal;
import com.googlecode.dni.internal.DniTypes;
import com.googlecode.dni.internal.NativeUnsafeAccess;
import com.googlecode.dni.internal.library.DynamicLibrary;
import com.googlecode.dni.internal.library.SymbolNotFoundException;
import com.googlecode.dni.type.Pointer;
import com.googlecode.dni.type.nativetype.NativeTypes;

/**
 * <p>
 *  Provides a faster Windows-1252 encoding than that in the default platform.
 * </p>
 *
 * @author Matthew Wilson
 */
public final class Windows1252Coder
{

    private static final String SYMBOL_NAME = "windows_1252";

    /**
     * Threshold of string length above which strings are processed in native
     * code.  This value was found empirically.
     */
    private static final int NATIVE_THRESHOLD = 18;

    private static final long FORWARD_TABLE;

    private static final int FORWARD_TABLE_LENGTH;

    private static final long REVERSE_TABLE;

    private static final ByteBuffer FORWARD_TABLE_BUFFER;

    static
    {
        DynamicLibrary library = DniInternal.PLATFORM.getDniDynamicLibrary();

        try
        {
            ByteBuffer ftBuffer = library.getSymbol( "dni_charset_forward_table_" + SYMBOL_NAME ).getBuffer( 8 );
            ByteBuffer ftlBuffer = library.getSymbol( "dni_charset_forward_table_length_" + SYMBOL_NAME ).getBuffer( 4 );
            ByteBuffer rtBuffer = library.getSymbol( "dni_charset_reverse_table_" + SYMBOL_NAME ).getBuffer( 8 );
            if ( NativeTypes.POINTER_SIZE == 4 )
            {
                FORWARD_TABLE = ftBuffer.getInt() & 0xffffffffL;
                REVERSE_TABLE = rtBuffer.getInt() & 0xffffffffL;
            }
            else
            {
                FORWARD_TABLE = ftBuffer.getLong();
                REVERSE_TABLE = rtBuffer.getLong();
            }
            FORWARD_TABLE_LENGTH = ftlBuffer.getInt();
        }
        catch ( SymbolNotFoundException exception )
        {
            throw new ExceptionInInitializerError( exception );
        }

        FORWARD_TABLE_BUFFER = new Pointer( FORWARD_TABLE ).getBuffer( FORWARD_TABLE_LENGTH );
    }


    /** Prevents instantiation. */
    private Windows1252Coder()
    {
        throw new UnsupportedOperationException();
    }

    /**
     * Obtains an upper bound on the space required for the given string with a
     * null terminator.
     *
     * @param string
     *            the string
     * @return the size in bytes
     */
    public static int getByteCount( final String string )
    {
        return string.length() + 1;
    }

    /**
     * Encodes the given string into the given buffer.  The position is then
     * undefined.
     *
     * @param string
     *            the string
     * @param buffer
     *            the buffer
     * 
     * @return the number of bytes used (including terminator)
     */
    public static int encode( final String string,
                              final ByteBuffer buffer )
    {
        if ( string.length() > NATIVE_THRESHOLD )
        {
            return NativeUnsafeAccess.writeLookupString( string, buffer, buffer.position(), FORWARD_TABLE, FORWARD_TABLE_LENGTH );
        }

        for ( int i = 0; i < string.length(); i++ )
        {
            char c = string.charAt( i );
            buffer.put( c < FORWARD_TABLE_LENGTH ? FORWARD_TABLE_BUFFER.get( c ) : (byte) '?' );
        }

        buffer.put( (byte) 0 );
        
        return string.length() + 1;
    }

    /**
     * Reads a null-terminated string.
     *
     * @param pointer
     *            the start of the string
     *
     * @return a string
     */
    public static String decode( final long pointer )
    {
        if ( pointer == 0 )
        {
            return null;
        }

        // TODO can this be done in Java at all?

        return NativeUnsafeAccess.readLookupString( pointer, REVERSE_TABLE );
    }

    /**
     * {@link FastCharset} implementation for {@link Windows1252Coder}.
     *
     * @author Matthew Wilson
     */
    static final class Windows1252FastCharset extends FastCharset
    {
        /** Creates a new instance. */
        Windows1252FastCharset()
        {
            super( "Windows-1252",
                   DniTypes.INTERNAL_STRING_PACKAGE + "/" + "Windows1252Coder",
                   Charset.forName( "Windows-1252" )  );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int encode( final String string, final ByteBuffer buffer )
        {
            return Windows1252Coder.encode( string, buffer );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String decode( final long pointer )
        {
            return Windows1252Coder.decode( pointer );
        }
    }

}
